package com.alicloud.openservices.tablestore.jdbc;


import com.alicloud.openservices.tablestore.AsyncClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class OTSConnection extends WrapperAdapter implements Connection {

    private static final Logger LOGGER = LoggerFactory.getLogger(OTSConnection.class);

    static final String ACCESS_KEY_ID = "user";
    static final String ACCESS_KEY_SECRET = "password";
    static final String ENABLE_REQUEST_COMPRESSION = "enableRequestCompression";
    static final String ENABLE_RESPONSE_COMPRESSION = "enableResponseCompression";
    static final String ENABLE_RESPONSE_VALIDATION = "enableResponseValidation";
    static final String IO_THREAD_COUNT = "ioThreadCount";
    static final String MAX_CONNECTIONS = "maxConnections";
    static final String SOCKET_TIMEOUT_IN_MILLISECOND = "socketTimeoutInMillisecond";
    static final String CONNECTION_TIMEOUT_IN_MILLISECOND = "connectionTimeoutInMillisecond";
    static final String RETRY_THREAD_COUNT = "retryThreadCount";
    static final String ENABLE_RESPONSE_CONTENT_MD5_CHECKING = "enableResponseContentMD5Checking";
    static final String RETRY_STRATEGY = "retryStrategy";
    static final String RETRY_TIMEOUT = "retryTimeout";
    static final String RETRY_TIMEOUT_UNIT = "retryTimeoutUnit";
    static final String TIME_THRESHOLD_OF_TRACE_LOGGER = "timeThresholdOfTraceLogger";
    static final String TIME_THRESHOLD_OF_SERVER_TRACER = "timeThresholdOfServerTracer";
    static final String PROXY_HOST = "proxyHost";
    static final String PROXY_PORT = "proxyPort";
    static final String PROXY_USERNAME = "proxyUsername";
    static final String PROXY_PASSWORD = "proxyPassword";
    static final String PROXY_DOMAIN = "proxyDomain";
    static final String PROXY_WORKSTATION = "proxyWorkstation";
    static final String SYNC_CLIENT_WAIT_FUTURE_TIMEOUT_IN_MILLIS = "syncClientWaitFutureTimeoutInMillis";
    static final String CONNECTION_REQUEST_TIMEOUT_IN_MILLISECOND = "connectionRequestTimeoutInMillisecond";

    private final String url;
    OTSConnectionConfiguration config;
    AsyncClient otsClient;
    private Properties info;
    private boolean isClosed = false;
    private SQLWarning warnings = null;

    OTSConnection(String url, Properties info) throws SQLException {
        LOGGER.debug("create connection with url: " + url);
        this.url = url;
        this.info = info;
        config = OTSConnectionConfiguration.parse(url, info);
        otsClient = new AsyncClient(config.getEndPoint(), config.getAccessKeyId(), config.getAccessKeySecret(), config.getInstanceName(), config.getClientConfiguration());
    }

    @Override
    public OTSPreparedStatement prepareStatement(String sql) throws SQLException {
        checkClosed();
        return new OTSPreparedStatement(this, sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        // The parameter `autoGeneratedKeys` is ignored if the SQL statement is not an INSERT statement,
        // or an SQL statement able to return auto-generated keys.
        return prepareStatement(sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        // The driver ignore the array if the SQL statement is not an INSERT statement, or an SQL
        // statement able to return auto-generated keys.
        return prepareStatement(sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        // The driver ignore the array if the SQL statement is not an INSERT statement, or an SQL
        // statement able to return auto-generated keys.
        return prepareStatement(sql);
    }

    @Override
    public OTSPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public String nativeSQL(String sql) {
        return sql;
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        checkClosed();
        return true;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        checkClosed();
        if (!autoCommit) {
            throw new SQLFeatureNotSupportedException("disabling autocommit is not supported");
        }
    }

    @Override
    public void commit() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void rollback() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void close() {
        LOGGER.debug("close connection");
        if (!isClosed) {
            otsClient.shutdown();
            isClosed = true;
        }
    }

    @Override
    public boolean isClosed() {
        return isClosed;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        checkClosed();
        return new OTSDatabaseMetaData(this);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        checkClosed();
        return false;
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        checkClosed();
        if (readOnly) {
            throw new SQLFeatureNotSupportedException("enabling read-only is not supported");
        }
    }

    @Override
    public String getCatalog() throws SQLException {
        checkClosed();
        return config.getInstanceName();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        LOGGER.debug("setCatalog");
        checkClosed();
        config.setInstanceName(catalog);
        reconnect();
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return Connection.TRANSACTION_NONE;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return warnings;
    }

    @Override
    public void clearWarnings() throws SQLException {
        warnings = null;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int getHoldability() throws SQLException {
        checkClosed();
        return ResultSet.HOLD_CURSORS_OVER_COMMIT;
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
        checkClosed();
        // Always hold cursors over commit.
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public OTSStatement createStatement() throws SQLException {
        checkClosed();
        return new OTSStatement(this);
    }

    @Override
    public OTSStatement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Clob createClob() throws SQLException {
        return new SerialClob(new char[]{});
    }

    @Override
    public Blob createBlob() throws SQLException {
        return new SerialBlob(new byte[]{});
    }

    @Override
    public NClob createNClob() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return !isClosed;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        LOGGER.debug("setClientInfo");
        try {
            checkClosed();
            info.setProperty(name, value);
            reconnect();
        } catch (SQLException e) {
            throw new SQLClientInfoException(e.getMessage(), null);
        }
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        checkClosed();
        return info;
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        LOGGER.debug("setClientInfo");
        try {
            checkClosed();
            info = properties;
            reconnect();
        } catch (SQLException e) {
            throw new SQLClientInfoException(e.getMessage(), null);
        }
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return info.getProperty(name);
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public String getSchema() throws SQLException {
        checkClosed();
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {
        checkClosed();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        // Do nothing because the driver does not support setting network timeout dynamically.
        // No exception because HikariCP will call this method to check connection aliveness.
    }

    @Override
    public int getNetworkTimeout() {
        return config.getClientConfiguration().getConnectionRequestTimeoutInMillisecond();
    }

    private void reconnect() throws SQLException {
        LOGGER.debug("reconnect");
        otsClient.shutdown();
        config = OTSConnectionConfiguration.parse(url, info);
        otsClient = new AsyncClient(config.getEndPoint(), config.getAccessKeyId(), config.getAccessKeySecret(), config.getInstanceName(), config.getClientConfiguration());
        LOGGER.debug("reconnect success");
    }

    private void checkClosed() throws SQLException {
        if (isClosed) {
            throw new SQLException("the connection has already been closed");
        }
    }
}
